
#define _NAND_TEST_DEV_C_

#include "nand_type.h"
#include "nand_blk.h"
#include "nand_dev.h"

struct nand_kobject* s_nand_kobj2;
extern struct _nand_info* p_nand_info;
extern struct kobj_type ktype;
extern struct _nftl_blk nftl_blk_head;

extern struct _nand_partition* build_nand_partition(struct _nand_phy_partition* phy_partition);
extern void add_nftl_blk_list(struct _nftl_blk*head,struct _nftl_blk *nftl_blk);
extern uint16 get_partitionNO(struct _nand_phy_partition* phy_partition);
extern struct _nand_phy_partition* get_head_phy_partition_from_nand_info(struct _nand_info*nand_info);
extern struct _nand_phy_partition* get_next_phy_partition(struct _nand_phy_partition* phy_partition);
extern void NAND_Interrupt(__u32 nand_index);
extern __u32 NAND_GetCurrentCH(void);
extern int nand_ftl_exit(void);
extern unsigned int nftl_read(unsigned int start_sector,unsigned int len,unsigned char *buf);
extern unsigned int nftl_write(unsigned int start_sector,unsigned int len,unsigned char *buf);
extern unsigned int nftl_flush_write_cache(void);
extern int nand_ftl_exit(void);

extern uint32 gc_all(struct _nftl_zone* zone);
extern uint32 gc_one(struct _nftl_zone* zone);
extern void print_nftl_zone(struct _nftl_zone * zone);
extern void print_free_list(struct _nftl_zone* zone);
extern void print_block_invalid_list(struct _nftl_zone* zone);
extern uint32 nftl_set_zone_test(void * _zone,uint32 num);

static ssize_t nand_test_store(struct kobject *kobject,struct attribute *attr, const char *buf, size_t count);
static ssize_t nand_test_show(struct kobject *kobject,struct attribute *attr, char *buf);
void obj_test_release(struct kobject *kobject);

int add_nand_for_test(struct _nand_phy_partition* phy_partition);
int build_all_nftl(struct _nand_info*nand_info);

#define NFTL_SCHEDULE_TIMEOUT  (HZ >> 2)
#define NFTL_FLUSH_DATA_TIME	 1

struct attribute prompt_attr = {
    .name = "nand_debug",
    .mode = S_IRWXUGO
};

static struct attribute *def_attrs[] = {
    &prompt_attr,
    NULL
};


struct sysfs_ops obj_test_sysops =
{
    .show =  nand_test_show,
    .store = nand_test_store
};

struct kobj_type ktype =
{
    .release = obj_test_release,
    .sysfs_ops=&obj_test_sysops,
    .default_attrs=def_attrs
};

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nftl_test_thread(void *arg)
{
    struct _nftl_blk *nftl_blk = arg;
    unsigned long time;

    nftl_blk->time_flush = NFTL_FLUSH_DATA_TIME * HZ;
    //nftl_blk->time_flush = 30 * HZ;
    nftl_blk->time_flush = HZ;

    while (!kthread_should_stop()) {

        mutex_lock(nftl_blk->blk_lock);

        if(nftl_get_zone_write_cache_nums(nftl_blk->nftl_zone) > 32){
            time = jiffies;
           //if (time_after(time,nftl_blk->time + nftl_blk->time_flush)){
                nftl_blk->flush_write_cache(nftl_blk,8);
           //}
        }
//        else if(nftl_get_zone_write_cache_nums(nftl_blk->nftl_zone) > 200)
//        {
//           time = jiffies;
//           if (time_after(time,nftl_blk->time + nftl_blk->time_flush)){
//                nftl_blk->flush_write_cache(nftl_blk,6);
//           }
//        }
//        else if(nftl_get_zone_write_cache_nums(nftl_blk->nftl_zone) > 100)
//        {
//            time = jiffies;
//           if (time_after(time,nftl_blk->time + nftl_blk->time_flush)){
//                nftl_blk->flush_write_cache(nftl_blk,8);
//           }
//        }
        else
        {
            time = jiffies;
           if (time_after(time,nftl_blk->time + nftl_blk->time_flush + HZ)){
                nftl_blk->flush_write_cache(nftl_blk,2);
           }
        }

//#if  SUPPORT_WEAR_LEVELING
//        if(do_static_wear_leveling(nftl_blk->nftl_zone) != 0){
//            printk("nftl_thread do_static_wear_leveling error!\n");
//        }
//#endif

        if(garbage_collect(nftl_blk->nftl_zone) != 0){
            printk("nftl_thread garbage_collect error!\n");
        }

        if(do_prio_gc(nftl_blk->nftl_zone) != 0){
            printk("nftl_thread do_prio_gc error!\n");
        }

        mutex_unlock(nftl_blk->blk_lock);
        set_current_state(TASK_INTERRUPTIBLE);
        schedule_timeout(NFTL_SCHEDULE_TIMEOUT);
    }

    nftl_blk->nftl_thread = (void*)NULL;
    return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
#ifdef __LINUX_NAND_SUPPORT_INT__

spinlock_t     nand_int_test_lock;

static irqreturn_t nand_interrupt_ch0(int irq, void *dev_id)
{
    unsigned long iflags;
    __u32 nand_index;

    //printk("nand_interrupt_ch0!\n");
    spin_lock_irqsave(&nand_int_test_lock, iflags);

    nand_index = NAND_GetCurrentCH();
    if(nand_index!=0)
    {
        //printk(" ch %d int in ch0\n", nand_index);
    }
    else
    {
        NAND_Interrupt(nand_index);
    }
    spin_unlock_irqrestore(&nand_int_test_lock, iflags);

    return IRQ_HANDLED;
}

static irqreturn_t nand_interrupt_ch1(int irq, void *dev_id)
{
    unsigned long iflags;
    __u32 nand_index;

    //printk("nand_interrupt_ch1!\n");

    spin_lock_irqsave(&nand_int_test_lock, iflags);
    nand_index = NAND_GetCurrentCH();
    if(nand_index!=1)
    {
        //printk(" ch %d int in ch1\n", nand_index);
    }
    else
    {
        NAND_Interrupt(nand_index);
    }
    spin_unlock_irqrestore(&nand_int_test_lock, iflags);

    return IRQ_HANDLED;
}
#endif

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_interrupt_init(void)
{
#ifdef __LINUX_NAND_SUPPORT_INT__

    char * dev_name = "nand_dev";
    char * dev_id = "nand_id";

    unsigned long irqflags_ch0, irqflags_ch1;
    //printk("[NAND] nand driver version: 0x%x 0x%x, support int! \n", NAND_VERSION_0,NAND_VERSION_1);

    spin_lock_init(&nand_int_test_lock);
    irqflags_ch0 = IRQF_DISABLED;
    irqflags_ch1 = IRQF_DISABLED;

	#ifdef SUN8IW1P1
		printk("[NAND]run on A31\n");
	#endif

	#ifdef SUN8IW3P1
		printk("[NAND]run on A23\n");
	#endif

	#ifdef SUN9IW1P1
		printk("[NAND]run on A33\n");
	#endif

	#ifdef SUN8IW1P1
		if (request_irq(SUNXI_IRQ_NAND0, nand_interrupt_ch0, IRQF_DISABLED, dev_name, &dev_id))
		{
		    printk("nand interrupte ch0 irqno: %d register error\n", SUNXI_IRQ_NAND0);
		    return -EAGAIN;
		}
		else
		{
		    printk("nand interrupte ch0 irqno: %d register ok\n", SUNXI_IRQ_NAND0);
		}

		if (request_irq(SUNXI_IRQ_NAND1, nand_interrupt_ch1, IRQF_DISABLED, dev_name, &dev_id))
		{
		    printk("nand interrupte ch1, irqno: %d register error\n", SUNXI_IRQ_NAND1);
		    return -EAGAIN;
		}
		else
		{
		    printk("nand interrupte ch1, irqno: %d register ok\n", SUNXI_IRQ_NAND1);
		}
	#endif

	#ifdef SUN8IW3P1
		if (request_irq(SUNXI_IRQ_NAND, nand_interrupt_ch0, IRQF_DISABLED, dev_name, &dev_id))
		{
		    printk("nand interrupte ch0 irqno: %d register error\n", SUNXI_IRQ_NAND);
		    return -EAGAIN;
		}
		else
		{
		    //printk("nand interrupte ch0 irqno: %d register ok\n", SUNXI_IRQ_NAND0);
		}
	#endif

#endif
    return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int build_all_nftl(struct _nand_info*nand_info)
{
	struct _nand_phy_partition*  phy_partition;

	phy_partition = get_head_phy_partition_from_nand_info(nand_info);

    nftl_blk_head.nftl_blk_next = NULL;
	while(phy_partition != NULL)
	{
		if(add_nand_for_test(phy_partition) != 0)
		{
		    printk("build_all_nftl fail!\n");
			return 1;
		}
		phy_partition = get_next_phy_partition(phy_partition);
	}

	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int add_nand_for_test(struct _nand_phy_partition* phy_partition)
{
	int i;
    __u32 cur_offset = 0;
    struct _nftl_blk *nftl_blk;
    struct _nand_dev *nand_dev;
	struct _nand_disk* disk;
	struct _nand_disk* head_disk;
//	struct nand_kobject* nand_kobj;
	uint16 PartitionNO;

    PartitionNO = get_partitionNO(phy_partition);

    nftl_blk = kmalloc(sizeof(struct _nftl_blk), GFP_KERNEL);
    if (!nftl_blk)
	{
	    printk("nftl_blk kmalloc fail!\n");
		return 1;
	}
    nftl_blk->nand = build_nand_partition(phy_partition);

    if (nftl_initialize(nftl_blk,PartitionNO))
	{
        printk("nftl_initialize failed\n");
        return 1;
    }

    nftl_blk->blk_lock = kmalloc(sizeof(struct mutex), GFP_KERNEL);
    if (!nftl_blk->blk_lock)
	{
	    printk("blk_lock kmalloc fail!\n");
		return 1;
	}
    mutex_init(nftl_blk->blk_lock);

	nftl_blk->nftl_thread = kthread_run(nftl_test_thread, nftl_blk, "%sd", "nftl");
    if (IS_ERR(nftl_blk->nftl_thread))
    {
        printk("nftl_thread kthread_run fail!\n");
        return 1;
    }

    add_nftl_blk_list(&nftl_blk_head,nftl_blk);

    s_nand_kobj2 = kzalloc(sizeof(struct nand_kobject), GFP_KERNEL);
    if (!s_nand_kobj2)
	{
	    printk("nand_kobj kzalloc fail!\n");
		return 1;
	}
	s_nand_kobj2->nftl_blk = nftl_blk;
    if(kobject_init_and_add(&s_nand_kobj2->kobj,&ktype,NULL,"nand_driver%d",PartitionNO) != 0 ) {
	printk("init nand sysfs fail!\n");
		return 1;
	}

    return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_driver_test_init(void)
{
    int ret = 0;
    struct _nand_info* p_nand_info2;

    ret = nand_interrupt_init();
    if(ret != 0)
    {
        printk("nand_driver_test_init fail1!\n");
        return ret;
    }

    p_nand_info2 = NandHwInit();
    if(p_nand_info2 == NULL)
    {
        printk("nand_driver_test_init fail2!\n");
        NandHwExit();
        return 1;
    }

    ret = nand_info_init(p_nand_info2,0,8,NULL);
    if(ret != 0)
    {
        printk("nand_driver_test_init fail3!\n");
        return ret;
    }

    ret = build_all_nftl(p_nand_info2);
    if(ret != 0)
    {
        printk("nand_driver_test_init fail4!\n");
        nand_ftl_exit();
        return ret;
    }
    return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_driver_test_exit(void)
{
    nftl_flush_write_cache();
    nand_ftl_exit();
    NandHwExit();
    return 0;
}


/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nftl_test_read(uint32 start_sector,uint32 len,unsigned char *buf)
{
    int ret = 0;
    struct _nftl_blk *nftl_blk;
    nftl_blk = nftl_blk_head.nftl_blk_next;

    if(len == 0)
        return 0;

    while(start_sector >= nftl_blk->nftl_logic_size)
    {
        start_sector -= nftl_blk->nftl_logic_size;
        nftl_blk = nftl_blk->nftl_blk_next;
        if(nftl_blk == NULL)
        {
            printk("[NE]parameter error %d,%d !\n",start_sector,len);
            return 1;
        }
    }

    mutex_lock(nftl_blk->blk_lock);
    ret = nftl_blk->read_data(nftl_blk,start_sector,len,buf);
    mutex_unlock(nftl_blk->blk_lock);

    return  ret;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
uint32 nftl_test_write(uint32 start_sector,uint32 len,unsigned char *buf)
{
    int ret = 0;
    struct _nftl_blk *nftl_blk;
    nftl_blk = nftl_blk_head.nftl_blk_next;

    if(len == 0)
        return 0;

    while(start_sector >= nftl_blk->nftl_logic_size)
    {
        start_sector -= nftl_blk->nftl_logic_size;
        nftl_blk = nftl_blk->nftl_blk_next;
        if(nftl_blk == NULL)
        {
            printk("[NE]parameter error %d,%d !\n",start_sector,len);
            return 1;
        }
    }

    mutex_lock(nftl_blk->blk_lock);
    ret = nftl_blk->write_data(nftl_blk,start_sector,len,buf);
    mutex_unlock(nftl_blk->blk_lock);

    return ret;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
uint32 nftl_test_flush_write_cache(void)
{
    struct _nftl_blk *nftl_blk;
    nftl_blk = nftl_blk_head.nftl_blk_next;

    while(nftl_blk != NULL)
    {
        mutex_lock(nftl_blk->blk_lock);
        nftl_blk->flush_write_cache(nftl_blk,0xffff);
        mutex_unlock(nftl_blk->blk_lock);
        nftl_blk = nftl_blk->nftl_blk_next;
    }
	return 0;
}


/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
void obj_test_release(struct kobject *kobject)
{
    printk("release");
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static ssize_t nand_test_show(struct kobject *kobject,struct attribute *attr, char *buf)
{
    ssize_t count = 0;
    struct nand_kobject* nand_kobj;

    nand_kobj = (struct nand_kobject*)kobject;

    print_nftl_zone(nand_kobj->nftl_blk->nftl_zone);

    return count;
}

/*****************************************************************************
*Name         :
*Description  :receive testcase num from echo command
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static ssize_t nand_test_store(struct kobject *kobject,struct attribute *attr, const char *buf, size_t count)
{
    int             ret;
    int             argnum = 0;
    char            cmd[32] = {0};
    unsigned int    param0 = 0;
    unsigned int    param1 = 0;
    unsigned int    param2 = 0;

    struct nand_kobject* nand_kobj;
    nand_kobj = (struct nand_kobject*)kobject;

    argnum = sscanf(buf, "%s %u %u %u ", cmd, &param0, &param1, &param2);
    printk("argnum=%i, cmd=%s, param0=%u, param1=%u, param2=%u\n", argnum, cmd, param0, param1, param2);

    if (-1 == argnum)
    {
        printk("cmd format err!");
        goto NAND_TEST_STORE_EXIT;
    }

    if(strcmp(cmd,"help") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  help \n");
    }
    else if(strcmp(cmd,"flush") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  flush \n");
        mutex_lock(nand_kobj->nftl_blk->blk_lock);
        ret = nand_kobj->nftl_blk->flush_write_cache(nand_kobj->nftl_blk,param0);
        mutex_unlock(nand_kobj->nftl_blk->blk_lock);
        goto NAND_TEST_STORE_EXIT;
    }
    else if(strcmp(cmd,"gcall") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  gcall \n");
        mutex_lock(nand_kobj->nftl_blk->blk_lock);
        ret = gc_all(nand_kobj->nftl_blk->nftl_zone);
        mutex_unlock(nand_kobj->nftl_blk->blk_lock);
        goto NAND_TEST_STORE_EXIT;
    }
    else if(strcmp(cmd,"gcone") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  gcone \n");
        mutex_lock(nand_kobj->nftl_blk->blk_lock);
        ret = gc_one(nand_kobj->nftl_blk->nftl_zone);
        mutex_unlock(nand_kobj->nftl_blk->blk_lock);
        goto NAND_TEST_STORE_EXIT;
    }
    else if(strcmp(cmd,"test") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  test \n");
        mutex_lock(nand_kobj->nftl_blk->blk_lock);
        ret = nftl_set_zone_test((void*)nand_kobj->nftl_blk->nftl_zone,param0);
        mutex_unlock(nand_kobj->nftl_blk->blk_lock);
        goto NAND_TEST_STORE_EXIT;
    }
    else if(strcmp(cmd,"showall") == 0)
    {
        printk("nand debug cmd:\n");
        printk("  show all \n");
        print_free_list(nand_kobj->nftl_blk->nftl_zone);
        print_block_invalid_list(nand_kobj->nftl_blk->nftl_zone);
        print_nftl_zone(nand_kobj->nftl_blk->nftl_zone);
        goto NAND_TEST_STORE_EXIT;
    }
    else
    {
        printk("err, nand debug undefined cmd: %s\n", cmd);
    }

NAND_TEST_STORE_EXIT:
    return count;
}
